/*  This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.
    For more details, see the GNU Lesser General Public License (www.fsf.org
    or the COPYING file somewhere in the package)
 */

/*! @file test/LifeCycle.cc
    @brief Class LifeCycle definition.
    @author @ref Guillaume_Terrissol
    @date 23rd February 2010 - 7th April 2014
    @note This file is distributed under the LGPL license.
    Refer to the file COPYING (or http://www.fsf.org) for more information.
 */

#include <cppunit/extensions/HelperMacros.h>

#include <BundleInstallerService.hh>
#include <BundleMgr.hh>

#include "Utils.hh"

namespace CHK
{
//------------------------------------------------------------------------------
//                                  Test Class
//------------------------------------------------------------------------------

    /*! @brief Bundle life cycle checks.
        @version 0.9
     */
    class LifeCycle : public CppUnit::TestFixture
    {
#ifndef NOT_FOR_DOXYGEN
        // Tests registering
        CPPUNIT_TEST_SUITE(LifeCycle);
        CPPUNIT_TEST(testingNominal);
        CPPUNIT_TEST(testingNominalBundles);
        CPPUNIT_TEST(testingSingleBundle);
        CPPUNIT_TEST(testingSingleBundleFailed);
        CPPUNIT_TEST(testingRecursiveBundles);
        CPPUNIT_TEST_SUITE_END();
#endif  // NOT_FOR_DOXYGEN
    public:
        //! @name "Interface"
        //@{
virtual         ~LifeCycle() = default;         //!< Destructor.
virtual void    setUp() override;               //!< Preparation.
        //@}

    private:
        //! @name Testing
        //@{
        void    testingNominal();               //!< 
        void    testingNominalBundles();        //!< 
        void    testingSingleBundle();          //!< ... a single bundle life cycle.
        void    testingSingleBundleFailed();    //!< ... a single bundle life cycle transitions.
        void    testingRecursiveBundles();      //!< ... an extended life cycle.
        //@}
    };


    CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(LifeCycle, "Bundle life cycle");


//------------------------------------------------------------------------------
//                                  "Interface"
//------------------------------------------------------------------------------

    /*! Cleanup between tests.
     */
    void LifeCycle::setUp()
    {
        removeBundles();
    }


//------------------------------------------------------------------------------
//                                     Tests
//------------------------------------------------------------------------------

    /*!
     */
    void LifeCycle::testingNominal()
    {
        installBundle("fr.osgi.test.1st_2.1.3.bndl");
        installBundle("fr.osgi.test.2nd_2.2.4.bndl");
        installBundle("fr.osgi.test.3rd_1.9.0.bndl");

        auto    lMgr    = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        auto    l1st    = lMgr->context()->findBundle("fr.osgi.test.1st");
        CPPUNIT_ASSERT_MESSAGE("Missing fr.osgi.test.1st bundle",
                               l1st);
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.1st inactive",
                               l1st->state() == "active");

        auto    l2nd    = lMgr->context()->findBundle("fr.osgi.test.2nd");
        CPPUNIT_ASSERT_MESSAGE("Missing fr.osgi.test.2nd bundle",
                               l2nd);
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.2nd inactive",
                               l2nd->state() == "active");

        auto    l3rd    = lMgr->context()->findBundle("fr.osgi.test.3rd");
        CPPUNIT_ASSERT_MESSAGE("Missing fr.osgi.test.3rd bundle",
                               l3rd);
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.3rd inactive",
                               l3rd->state() == "active");

        lMgr->finalize();

        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.1st still installed",
                               l1st->state() == "uninstalled");
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.2nd still installed",
                               l2nd->state() == "uninstalled");
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.3rd still installed",
                               l3rd->state() == "uninstalled");

        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.1st_2.1.3.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.2nd_2.2.4.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.3rd_1.9.0.bndl installed\n"
                               "Bundle fr.osgi.test.1st resolved\n"
                               "Bundle fr.osgi.test.2nd resolved\n"
                               "Bundle fr.osgi.test.3rd resolved\n"
                               "Starting N1ST::Activator\n"
                               "Starting N2ND::Activator\n"
                               "Starting N3RD::Activator\n"
                               "Stopping N3RD::Activator\n"
                               "Stopping N2ND::Activator\n"
                               "Stopping N1ST::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*!
     */
    void LifeCycle::testingNominalBundles()
    {
        installBundle("fr.osgi.test.root_1.0.0.bndl");
        installBundle("fr.osgi.test.needroot_1.0.0.bndl");
        installBundle("fr.osgi.test.dep2ndlevel_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        auto    l1st    = lMgr->context()->findBundle("fr.osgi.test.root");
        CPPUNIT_ASSERT_MESSAGE("Missing fr.osgi.test.root bundle",
                               l1st);
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.root inactive",
                               l1st->state() == "active");

        auto    l2nd    = lMgr->context()->findBundle("fr.osgi.test.needroot");
        CPPUNIT_ASSERT_MESSAGE("Missing fr.osgi.test.needroot bundle",
                               l2nd);
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.needroot inactive",
                               l2nd->state() == "active");

        auto    l3rd    = lMgr->context()->findBundle("fr.osgi.test.dep2ndlevel");
        CPPUNIT_ASSERT_MESSAGE("Missing fr.osgi.test.dep2ndlevel bundle",
                               l3rd);
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.dep2ndlevel inactive",
                               l3rd->state() == "active");

        lMgr->finalize();

        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.root still installed",
                               l1st->state() == "uninstalled");
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.needroot still installed",
                               l2nd->state() == "uninstalled");
        CPPUNIT_ASSERT_MESSAGE("Bundle fr.osgi.test.dep2ndlevel still installed",
                               l3rd->state() == "uninstalled");

        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.dep2ndlevel_1.0.0.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.needroot_1.0.0.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.root_1.0.0.bndl installed\n"
                               "Bundle fr.osgi.test.dep2ndlevel resolved\n"
                               "Starting ROOT::Activator\n"
                               "Starting NEEDROOT::Activator\n"
                               "Starting DEP2::Activator\n"
                               "Stopping DEP2::Activator\n"
                               "Stopping NEEDROOT::Activator\n"
                               "Stopping ROOT::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking a single bundle life cycle.
        The nominal life cycle of a single bundle is checked, step by step.
     */
    void LifeCycle::testingSingleBundle()
    {
        installBundle("fr.osgi.test.alone_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });

        auto    lInstaller = lMgr->services()->findByTypeAndName<OSGi::BundleInstallerService>("osgi.core.installer");
        CPPUNIT_ASSERT_MESSAGE("Bundle installer service is missing",
                               lInstaller);

        auto    lPath = lMgr->properties()->get("application.dir") + "/../bundles/";
        auto    lBndl   = lInstaller->installBundle(lPath + "fr.osgi.test.alone_1.0.0.bndl");
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected installed",
                               lBndl->state() == OSGi::kInstalled);

        lBndl->act(OSGi::kResolve, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected resolved",
                               lBndl->state() == OSGi::kResolved);

        lBndl->act(OSGi::kStart, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected active",
                               lBndl->state() == OSGi::kActive);

        lBndl->act(OSGi::kStop, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected resolved",
                               lBndl->state() == OSGi::kResolved);

        lBndl->act(OSGi::kUninstall, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected installed",
                               lBndl->state() == OSGi::kUninstalled);

        lBndl = lInstaller->replaceBundle("fr.osgi.test.alone", lPath + "fr.osgi.test.alone_1.0.0.bndl");
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected installed",
                               lBndl->state() == OSGi::kResolved);

        lBndl->act(OSGi::kUninstall, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected installed",
                               lBndl->state() == OSGi::kUninstalled);

        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Starting ALONE::Activator\n"
                               "Stopping ALONE::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking a single bundle life cycle transitions.
        For a single bundle, checks that only valid transitions can occur.
     */
    void LifeCycle::testingSingleBundleFailed()
    {
        installBundle("fr.osgi.test.alone_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });

        auto    lInstaller = lMgr->services()->findByTypeAndName<OSGi::BundleInstallerService>("osgi.core.installer");
        CPPUNIT_ASSERT_MESSAGE("Bundle installer service is missing",
                               lInstaller);

        auto    lPath = lMgr->properties()->get("application.dir") + "/../bundles/";
        auto    lBndl   = lInstaller->installBundle(lPath + "fr.osgi.test.alone_1.0.0.bndl");
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected installed",
                               lBndl->state() == OSGi::kInstalled);

        lBndl->act(OSGi::kStart, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected installed",
                               lBndl->state() == OSGi::kInstalled);

        lBndl->act(OSGi::kStop, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected installed",
                               lBndl->state() == OSGi::kInstalled);

        lBndl->act(OSGi::kResolve, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected resolved",
                               lBndl->state() == OSGi::kResolved);

        lBndl->act(OSGi::kStop, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected resolved",
                               lBndl->state() == OSGi::kResolved);

        lBndl->act(OSGi::kStart, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected active",
                               lBndl->state() == OSGi::kActive);

        lBndl->act(OSGi::kResolve, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected active",
                               lBndl->state() == OSGi::kActive);

        lBndl->act(OSGi::kUninstall, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected active",
                               lBndl->state() == OSGi::kActive);

        lBndl->act(OSGi::kStop, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected resolved",
                               lBndl->state() == OSGi::kResolved);

        lBndl->act(OSGi::kStop, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Invalid state; expected resolved",
                               lBndl->state() == OSGi::kResolved);

        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Starting ALONE::Activator\n"
                               "Stopping ALONE::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking dependent bundles, with an extended life cycle.
        Dependent bundles are created with a factory creating "suspendable" bundles.@n
        The root bundle is paused, then resumed.@n
        The status of dependent bundles is checked.
     */
    void LifeCycle::testingRecursiveBundles()
    {
        installBundle("fr.osgi.test.root_1.0.0.bndl");
        installBundle("fr.osgi.test.needroot_1.0.0.bndl");
        installBundle("fr.osgi.test.dep2ndlevel_1.0.0.bndl");

        auto    lFactory = std::make_shared<ValidBundleFactory>();
        auto    lMgr     = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" }, lFactory);
        lMgr->loadBundles();
        lMgr->startBundles();

        auto    lRoot       = lMgr->context()->findBundle("fr.osgi.test.root");
        bool    lIsPaused   = lRoot->act(kPause, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Failed to pause bundles",
                               lIsPaused);
        bool    lIsResumed  = lRoot->act(kResume, lMgr->context());
        CPPUNIT_ASSERT_MESSAGE("Failed to resume bundles",
                               lIsResumed);
        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Installed fr.osgi.test.dep2ndlevel bundle\n"
                               "Bundle bundles/fr.osgi.test.dep2ndlevel_1.0.0.bndl installed\n"
                               "Installed fr.osgi.test.needroot bundle\n"
                               "Bundle bundles/fr.osgi.test.needroot_1.0.0.bndl installed\n"
                               "Installed fr.osgi.test.root bundle\n"
                               "Bundle bundles/fr.osgi.test.root_1.0.0.bndl installed\n"
                               "Bundle fr.osgi.test.dep2ndlevel resolved\n"

                               "Starting ROOT::Activator\n"
                               "Starting NEEDROOT::Activator\n"
                               "Starting DEP2::Activator\n"

                               "fr.osgi.test.dep2ndlevel is now paused\n"
                               "fr.osgi.test.needroot is now paused\n"
                               "fr.osgi.test.root is now paused\n"

                               "Resuming fr.osgi.test.root ...\n"
                               "Resuming fr.osgi.test.needroot ...\n"
                               "Resuming fr.osgi.test.dep2ndlevel ...\n"
                               "Stopping DEP2::Activator\n"
                               "Stopping NEEDROOT::Activator\n"
                               "Stopping ROOT::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }
}
